package com.jingdianjichi.subject.infra.basic.ES.自定义ES_鸡翅.service.impl;

import com.jingdianjichi.subject.common.entity.PageResult;
import com.jingdianjichi.subject.common.enums.SubjectInfoTypeEnum;
import com.jingdianjichi.subject.infra.basic.ES.自定义ES_鸡翅.ESRestClient;
import com.jingdianjichi.subject.infra.basic.ES.自定义ES_鸡翅.common.ESIndexInfo;
import com.jingdianjichi.subject.infra.basic.ES.自定义ES_鸡翅.common.ESSearchRequest;
import com.jingdianjichi.subject.infra.basic.ES.自定义ES_鸡翅.common.ESSourceData;
import com.jingdianjichi.subject.infra.basic.ES.自定义ES_鸡翅.entity.ESSubjectFields;
import com.jingdianjichi.subject.infra.basic.ES.自定义ES_鸡翅.entity.SubjectInfoES;
import com.jingdianjichi.subject.infra.basic.ES.自定义ES_鸡翅.service.SubjectESService;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.collections4.MapUtils;
import org.elasticsearch.action.search.SearchResponse;
import org.elasticsearch.common.text.Text;
import org.elasticsearch.index.query.BoolQueryBuilder;
import org.elasticsearch.index.query.MatchQueryBuilder;
import org.elasticsearch.index.query.QueryBuilders;
import org.elasticsearch.search.SearchHit;
import org.elasticsearch.search.SearchHits;
import org.elasticsearch.search.fetch.subphase.highlight.HighlightBuilder;
import org.elasticsearch.search.fetch.subphase.highlight.HighlightField;
import org.springframework.stereotype.Service;
import org.springframework.util.CollectionUtils;

import java.math.BigDecimal;
import java.math.RoundingMode;
import java.util.*;

@Service
@Slf4j
public class SubjectESServiceImpl implements SubjectESService {


    //插入文档
    @Override
    public boolean insert(SubjectInfoES subjectInfoES) {
        //交互我们都用SourceData来交互亲
        ESSourceData sourceData = new ESSourceData();

        //转换一下
        Map<String,Object> data=convertESSourceData(subjectInfoES);

        //封装数据
        sourceData.setDocId(subjectInfoES.getDocId().toString());
        sourceData.setData(data);

        //指定存放的集群和数据
        boolean result = ESRestClient.insertDoc(getESIndexInfo(), sourceData);

        return result;
    }


    //分页查询从Es中来的亲,这里就要用到我们之前封装的ESSearchRequest了,去封装请求的参数亲
    @Override
    public PageResult<SubjectInfoES> querySubjectList(SubjectInfoES req) {
        PageResult<SubjectInfoES> pageResult=new PageResult<>();
        ESSearchRequest searchRequest=CreateSearchListQuery(req);

        //返回结果
        SearchResponse response = ESRestClient.searchWithTermQuery(getESIndexInfo(), searchRequest);

        //解析响应
        List<SubjectInfoES> infoESList=new ArrayList<>();
        SearchHits searchHits = response.getHits();

        //控的直接返回,就是没有命中
        if (searchHits == null || searchHits.getHits() ==null){
            pageResult.setPageNo(req.getPageNo());
            pageResult.setPageSize(req.getPageSize());
            pageResult.setRecords(infoESList);
            pageResult.setTotal(0);

            return pageResult;
        }

        //遍历
        SearchHit[] hits = searchHits.getHits();
        for (SearchHit hit : hits) {
            //转换对象
            SubjectInfoES subjectInfoES = convertResult(hit);

            //添加
            if (Objects.nonNull(subjectInfoES)){
                infoESList.add(subjectInfoES);
            }
        }

        //封装数据
        pageResult.setPageNo(req.getPageNo());
        pageResult.setPageSize(req.getPageSize());
        pageResult.setRecords(infoESList);
        pageResult.setTotal(Long.valueOf(searchHits.getTotalHits().value).intValue());      //转换一下


        return pageResult;
    }



    //每个service自己决定向那个集群和索引放数据奥
    private ESIndexInfo getESIndexInfo(){
        ESIndexInfo esIndexInfo = new ESIndexInfo();

        esIndexInfo.setClusterName("25f825fc68a3");     //有集群名称之后我们的客户端可以自己找到的亲
        esIndexInfo.setIndexName("subject_index");      //对应的索引库

        return esIndexInfo;
    }

    //转换成ES的源对象
    private Map<String, Object> convertESSourceData(SubjectInfoES subjectInfoES) {
        HashMap<String, Object> data = new HashMap<>();

        data.put(ESSubjectFields.SUBJECT_ID,subjectInfoES.getSubjectId());
        data.put(ESSubjectFields.DOC_ID,subjectInfoES.getDocId());
        data.put(ESSubjectFields.SUBJECT_NAME,subjectInfoES.getSubjectName());
        data.put(ESSubjectFields.SUBJECT_ANSWER,subjectInfoES.getSubjectAnswer());
        data.put(ESSubjectFields.CREATE_USER,subjectInfoES.getCreateUser());
        data.put(ESSubjectFields.SUBJECT_TYPE,subjectInfoES.getSubjectType());
        data.put(ESSubjectFields.CREATE_TIME,subjectInfoES.getCreateTime());

        return data;
    }

    //返回ES的请对象亲
    private ESSearchRequest CreateSearchListQuery(SubjectInfoES req) {
        ESSearchRequest searchRequest = new ESSearchRequest();
        BoolQueryBuilder bq = new BoolQueryBuilder();       //构造复杂查询亲

        //在题目和答案之间进行搜索

        MatchQueryBuilder subjectNameQueryBuilder = QueryBuilders.matchQuery(ESSubjectFields.SUBJECT_NAME, req.getKeyWord());  //这个keyWord就是用户在搜索框中输入的数据
        bq.should(subjectNameQueryBuilder);
        bq.boost(2);                            //增加权重   //我们看到的一般是标题命中的会优先展示的,其次是答案的,我们这里要提高题目的权重

        //简答题
        MatchQueryBuilder subjectAnswerQueryBuilder = QueryBuilders.matchQuery(ESSubjectFields.SUBJECT_ANSWER, req.getKeyWord()); //答案
        bq.should(subjectAnswerQueryBuilder);

        //必须是简答题
        MatchQueryBuilder subjectTypeQueryBuilder = QueryBuilders.matchQuery(ESSubjectFields.SUBJECT_TYPE, SubjectInfoTypeEnum.BRIEF.getCode()); //简答使用枚举来亲
        bq.must(subjectTypeQueryBuilder);

        //俩个should最少命中一个才能返回奥
        bq.minimumShouldMatch(1);


        //高亮显示
        HighlightBuilder highlightBuilder = new HighlightBuilder().field("*").requireFieldMatch(false);
        /*
             field是用于展示高亮的,如果匹配了,就把匹配的全部都高亮
             requireFieldMatch(false)   就是没有字段匹配时也不会报错的亲
         */
        //高亮主要靠他,为啥能变成红色奥,就是设置的标签
        highlightBuilder.preTags("<span style = \"color:red\">");
        highlightBuilder.postTags("</span>");


        //封装请求体
        searchRequest.setBq(bq);
        searchRequest.setHighlightBuilder(highlightBuilder);
        searchRequest.setFields(ESSubjectFields.FIELD_QUERYT);                 //设置搜索的字段奥
        searchRequest.setFrom((req.getPageNo()-1) * req.getPageSize());        //分页的开始
        searchRequest.setSize(req.getPageSize());
        searchRequest.setNeedScroll(false);                                    //不需要快照

        return searchRequest;
    }


    //转换成目标对象
    private SubjectInfoES convertResult(SearchHit hit) {
        Map<String, Object> sourceAsMap = hit.getSourceAsMap();

        //空的就返回
        if (CollectionUtils.isEmpty(sourceAsMap)){
            return null;
        }

        SubjectInfoES result = new SubjectInfoES();
        result.setSubjectId(MapUtils.getLong(sourceAsMap,ESSubjectFields.SUBJECT_ID));
        result.setSubjectName(MapUtils.getString(sourceAsMap,ESSubjectFields.SUBJECT_NAME));
        result.setSubjectAnswer(MapUtils.getString(sourceAsMap,ESSubjectFields.SUBJECT_ANSWER));
        result.setDocId(MapUtils.getLong(sourceAsMap,ESSubjectFields.DOC_ID));
        result.setSubjectType(MapUtils.getInteger(sourceAsMap,ESSubjectFields.SUBJECT_TYPE));
        //放入相关性算法,我们变成百分制的,好看,BigDecimal用于计算的亲,用于解决丢失精度的问题,是的，BigDecimal确实可以用于用户计算和数据存储
        result.setScore(new BigDecimal(String.valueOf(hit.getScore())).multiply(new BigDecimal("100.00")).setScale(2, RoundingMode.HALF_UP));
        //这个就是保留两位小数,并向上取整来的

        //获得高亮
        Map<String, HighlightField> highlightFields = hit.getHighlightFields();

        //获得题目名称,处理Name的高亮
        HighlightField subjectNameHighLightField = highlightFields.get(ESSubjectFields.SUBJECT_NAME);
        if (Objects.nonNull(subjectNameHighLightField)){
            Text[] fragments = subjectNameHighLightField.getFragments();   //这个就是获得高亮的段落

            StringBuilder nameSB=new StringBuilder();
            for (Text fragment : fragments) {
                nameSB.append(fragment);        //段落拼接进来
            }

            result.setSubjectName(nameSB.toString()); //覆盖原有的不是高亮的部分

        }

        //获得题目答案,处理answer的高亮
        HighlightField subjectAnswerHighLightField = highlightFields.get(ESSubjectFields.SUBJECT_ANSWER);
        if (Objects.nonNull(subjectAnswerHighLightField)){
            Text[] fragments = subjectAnswerHighLightField.getFragments();   //这个就是获得高亮的段落

            StringBuilder answerSB=new StringBuilder();
            for (Text fragment : fragments) {
                answerSB.append(fragment);        //段落拼接进来
            }

            result.setSubjectAnswer(answerSB.toString()); //覆盖原有的不是高亮的部分

        }

        //这里的高亮可能导致数据丢失,我们仅仅返回了高亮的部分给前端,前端去做展示即可,我们这里返回的也是,本来就是分页搜索
        //展示的就是简陋信息是可以这样返回的

        return result;
    }


}
